﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Web;
using NancyOauth2.Models;

namespace NancyOauth2.Domains
{
    public class AuthDomain:IAuthDomain
    {
        private  readonly List<ApplicationModel> _appInfos;
        private readonly List<string> _grantTypes;
        public AuthDomain(IApplicationDomain applicationDomain)
        {
            _appInfos = applicationDomain.GetValidApps().ToList();
            _grantTypes = BuildInGrantType().ToList();
        }

        /// <summary>
        /// 授权错误。
        /// </summary>
        public AuthErrorModel AuthorizeError()
        {
            var code = ErrorStatusCode.Access_Token_Has_Expired;

            return new AuthErrorModel()
            {
                Code = code,
            };
        }

        public bool CheckClientInfo(string clientId, string secret, string redirectUri, string granttype,string refreshtoken, out AuthErrorModel errorModel)
        {
            clientId = clientId.Trim();
            secret = secret.Trim();

            errorModel = new AuthErrorModel();

            if (string.IsNullOrEmpty(clientId) ||
                string.IsNullOrEmpty(secret) ||
                _appInfos.All(o => o.ClientId != clientId)
                )
            {
                errorModel.Code = ErrorStatusCode.Invalid_ApiKey;
                return false;
            }

            var apps = _appInfos.Where(o => o.ClientId == clientId).ToList();

            if (apps.All(o => !(o.ClientId == clientId && o.ClientSecret == secret)))
            {
                errorModel.Code = ErrorStatusCode.Client_Secret_Mismatch;
                return false;
            }

            var app = apps.First(o => o.ClientId == clientId && o.ClientSecret == secret);

            if (app.RedirectUri != redirectUri)
            {
                errorModel.Code = ErrorStatusCode.Redirect_Uri_Mismatche;
                return false;
            }

            if (_grantTypes.All(o=> o != granttype))
            {
                errorModel.Code = ErrorStatusCode.Unsupported_Grant_Type;
                return false;
            }


            //换新的token
            if (granttype == "refresh_token")
            {
                if (string.IsNullOrEmpty(refreshtoken) ||
                    !TokenModels.Any(o => o.Key == clientId && o.Value.RefreshToken == refreshtoken))
                {
                    errorModel.Code = ErrorStatusCode.Unsupported_Grant_Type;
                    return false;
                }
            }
            return true;
        }

        public TokenModel GetToken(string clientId, string clientSecret, string redirectUri, string grantType, string refreshToken)
        {
            if (grantType == "authorization_code")
            {
                //获取token
                return AccessToken(clientId, clientSecret, redirectUri);
            }
            else
            {

                return RefreshToken(clientId, clientSecret, redirectUri, refreshToken);
            }
        }

        private static readonly Dictionary<string,TokenModel>  TokenModels = new Dictionary<string, TokenModel>();

        private TokenModel AccessToken(string clientId, string clientSecret, string redirectUri)
        {
            TokenModel model = null;
            if (TokenModels.ContainsKey(clientId))
            {
                model = TokenModels[clientId];
            }
            else
            {
                model = new TokenModel()
                {
                    ClientId =  clientId
                };
                TokenModels.Add(clientId,model);
            }
            string accessToken, refreshToken, expiresIn;
            if (GenerateToken(clientSecret,out accessToken,out refreshToken,out expiresIn))
            {
                model.AccessToken = accessToken;
                model.RefreshToken = refreshToken;
                model.ExpiresIn = expiresIn;
            }

            return model;
        }
     

        private TokenModel RefreshToken(string clientId, string clientSecret, string redirectUri, string refreshToken)
        {
            if (!TokenModels.ContainsKey(clientId))
                return null;
            var model = TokenModels[clientId];
            if (model.RefreshToken != refreshToken)
                return null;


            string accessToken, expiresIn;
            if (GenerateToken(clientSecret, out accessToken, out refreshToken, out expiresIn))
            {
                model.AccessToken = clientSecret;
                model.RefreshToken = refreshToken;
                model.ExpiresIn = expiresIn;
            }

            return model;
        }

        private bool GenerateToken(string clientSecret, out string accessToken, out string refreshToken, out string expiresIn)
        {
            accessToken = Guid.NewGuid().ToString().Replace("-","");
            refreshToken = Guid.NewGuid().ToString().Replace("-", "");

            expiresIn = 3920.ToString();

            return true;
        }

        private IEnumerable<string> BuildInGrantType()
        {
            yield return "authorization_code";
            yield return "refresh_token";
        }

        /// <summary>
        /// 判断是否有效的token
        /// </summary>
        /// <param name="token">令牌</param>
        /// <param name="clientId">客户端ID</param>
        public static bool AllowToken(string token, out string clientId)
        {
            var model = TokenModels.FirstOrDefault(o => o.Value.AccessToken == token);
            
            if (model.Key == null)
            {
                clientId = null;
                return false;
            }

            clientId = model.Key;
            return true;
        }
    }

    /// <summary>
    /// 授权业务
    /// </summary>
    public interface IAuthDomain
    {
        AuthErrorModel AuthorizeError();

        /// <summary>
        /// 检查Client Id 是否合法
        /// </summary>
        bool CheckClientInfo(string clientId, string secret,string redirectUri,string granttype, string refreshtoken,out AuthErrorModel errorModel);

        TokenModel GetToken(string clientId, string clientSecret, string redirectUri, string grantType, string refreshToken);
    }
}